package models;

import com.jamonapi.Monitor;
import com.jamonapi.MonitorFactory;
import models.annotations.Shard;
import org.apache.commons.lang.StringUtils;
import org.h2.util.JdbcUtils;
import play.Logger;
import play.db.DB;
import play.db.jpa.GenericModel;
import play.db.jpa.JPABase;
import utils.ShortUUID;
import utils.db.ContextForUpdate;
import utils.db.ModelHelper;

import javax.persistence.*;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;

@MappedSuperclass
public class BasicModel extends GenericModel {

    @Id
    public String id;

    @Column(name = "create_time")
    public Long createTime;

    @Column(name = "update_time")
    public Long updateTime;

    @Transient
    public boolean isModelNew = true;

    void beforeSave(boolean forceInsert) {
        long now = System.currentTimeMillis();
        if (createTime == null) {
            createTime = now;
        }
        if (forceInsert && StringUtils.isNotEmpty(id)) {
            isModelNew = true;
        } else if (StringUtils.isEmpty(id)) {
            id = generateId();
            isModelNew = true;
        } else {
            isModelNew = false;
        }
        updateTime = now;
    }

    @Override
    public <T extends JPABase> T save() {
        beforeSave(false);
        if (checkShard() && !isModelNew) {
            isModelNew = false;
            return updateWithShardColumn();
        } else {
            isModelNew = false;
            return super.save();
        }
    }

    /**
     * 强制插入
     * 由于save()会自动判断id是否存在来决定是insert还是update，所以本函数适合手工指定id的情况下强制要求插入一条记录
     *
     * @param <T>
     * @return
     */
    public <T extends JPABase> T insert() {
        beforeSave(true);
        if (checkShard() && !isModelNew) {
            isModelNew = false;
            return updateWithShardColumn();
        } else {
            isModelNew = false;
            return super.save();
        }
    }

    private boolean checkShard() {
        for (Field field : this.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(Shard.class)) {
                return true;
            }
        }
        return false;
    }

    private <T extends JPABase> T updateWithShardColumn() {
        ContextForUpdate context = ModelHelper.buildUpdateSql(this.getClass(), this);
        Monitor monitor = MonitorFactory.start("SQL:" + context.sql);

        Connection con = null;
        PreparedStatement stmt = null;

        monitor.start();
        try {
            PersistenceUnit pu = this.getClass().getAnnotation(PersistenceUnit.class);
            if (pu == null) {
                con = DB.getConnection();
            } else {
                con = DB.getConnection(pu.name());
            }
            Logger.info("SQL:%s", context.sql);
            stmt = con.prepareStatement(context.sql);
            List<Object> params = new ArrayList();
            params.addAll(context.dataParams);
            params.addAll(context.keyParams);
            ModelHelper.setPreparedStatementParams(stmt, params.toArray());
            stmt.executeUpdate();
        } catch (Exception e) {
            Logger.error(e, "Error in updateWithShardColumn");
        } finally {
            JdbcUtils.closeSilently(stmt);
        }
        monitor.stop();
        return (T) this;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    @Override
    public Object _key() {
        return getId();
    }

    protected String generateId() {
        return ShortUUID.generate();
    }

    public static enum BoolEnum {

        FALSE(0, "否"),
        TRUE(1, "是"),
        NO(0, "否"),
        YES(1, "是"),;

        public int value;
        public String description;

        private BoolEnum(int val, String desc) {
            this.value = val;
            this.description = desc;
        }

        public static BoolEnum get(Integer value) {
            for (BoolEnum v : values()) {
                if (value == v.value) {
                    return v;
                }
            }
            return null;
        }

        @Override
        public String toString() {
            return description;
        }
    }
}
